package lelouet.datacenter.thermal.impacts;

import gridlib.api.Machine;

import java.io.*;
import java.rmi.RemoteException;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.*;

/**
 * A map that handles impacts from one server to another.<br />
 * <ul>
 * <li>Uses the servers names for hashes</li>
 * <li>provide load and</li>
 * </ul>
 */
public class ImpactMap extends HashMap<String, HashMap<String, Double>> {

	private static final long serialVersionUID = 1L;

	public ImpactMap() {
	}

	public double getImpact(Machine from, Machine to) {
		try {
			return getImpact(from.getName(), to.getName());
		} catch (RemoteException e) {
			throw new UnsupportedOperationException(
					"TODO : handle this better :", e);
		}
	}

	public double getImpact(String from, String to) {
		HashMap<String, Double> values = get(from);
		if (values == null) {
			return 0.0f;
		}
		Double value = values.get(to);
		if (value == null) {
			return 0;
		} else {
			return value;
		}
	}

	public double setImpact(Machine from, Machine to, double impact) {
		try {
			return setImpact(from.getName(), to.getName(), impact);
		} catch (RemoteException e) {
			e.printStackTrace(System.err);
			throw new UnsupportedOperationException(
					"TODO : handle this better :", e);
		}
	}

	protected double setImpact(String from, String to, double impact) {
		HashMap<String, Double> values = get(from);
		if (values == null) {
			values = new HashMap<String, Double>();
			put(from, values);
		}
		Double ret = values.put(to, impact);
		if (ret == null) {
			return 0.0f;
		} else {
			return ret;
		}
	}

	/** architecture-independant new line */
	public static String newline = System.getProperty("line.separator");

	/**
	 * writes this map to the given writer, so it can be {@link #load(Reader)
	 * loaded} in another map.
	 */
	public void save(Writer out) throws IOException {
		for (Entry<String, HashMap<String, Double>> e : entrySet()) {
			out.write('\"');
			out.write(escapeDQuotes(e.getKey()));
			out.write("\" : ");
			for (Entry<String, Double> e2 : e.getValue().entrySet()) {
				out.write('\"');
				out.write(escapeDQuotes(e2.getKey()));
				out.write("\"=>");
				out.write(e2.getValue().toString());
				out.write(" ");
			}
			out.write(newline);
		}

	}

	/**
	 * writes the map to given file.Use {@link #load(String)} to reload it.
	 * 
	 * @param filename
	 *            the name of the file to load
	 * @see #save(Writer)
	 */
	public void save(String filename) throws IOException {
		FileWriter writer = new FileWriter(filename);
		BufferedWriter buff = new BufferedWriter(writer);
		save(buff);
		buff.close();
	}

	/**
	 * Try to load a given file defining an impact map.
	 * 
	 * @param filename
	 *            the path of the file to read data from
	 * @see this relies on {@link #load(Reader)}
	 */
	public Exception[] load(String filename) {
		try {
			FileReader reader = new FileReader(filename);
			return load(reader);
		} catch (Exception e) {
			return new Exception[] { e };
		}

	}

	/**
	 * Load a map from a reader.
	 * 
	 * @param reader
	 *            the reader to read data from. It is buffered if not an
	 *            instance of {@link BufferedReader} yet.
	 * @return an array of the exceptions caught while parsing the file. If
	 *         there has not been any problem, should return []
	 */
	public Exception[] load(Reader reader) {
		BufferedReader buff = null;
		List<Exception> ret = new ArrayList<Exception>();
		if (reader instanceof BufferedReader) {
			buff = (BufferedReader) reader;
		} else {
			buff = new BufferedReader(reader);
		}
		String line;
		do {
			try {
				line = buff.readLine();
			} catch (IOException e) {
				line = null;
				ret.add(e);
			}
			if (line != null) {
				// skip empty lines and comments
				if (line.isEmpty() || line.startsWith("#")) {
					continue;
				}
				try {
					decodeLine(line);
				} catch (Exception e) {
					ret.add(e);
				}
			}
		} while (line != null);
		try {
			buff.close();
		} catch (IOException e) {
			ret.add(e);
		}
		return ret.toArray(new Exception[] {});

	}

	protected static final String NAME_PATTERN = "\".*?[^\\\\]\"";

	protected static final String FLOAT_PATTERN = "[0-9]+(\\.?[0-9]*)?f?";

	protected void decodeLine(String line) {
		Matcher matcher = Pattern.compile(NAME_PATTERN).matcher(line);
		Matcher floatMatcher = Pattern.compile(FLOAT_PATTERN).matcher(line);
		if (matcher.find()) {
			String impacterName = matcher.group();
			impacterName = impacterName.substring(1, impacterName.length() - 1);
			while (matcher.find()) {
				String impactedName = matcher.group();
				impactedName = impactedName.substring(1,
						impactedName.length() - 1);
				if (floatMatcher.find(matcher.end())) {
					float val = Float.parseFloat(floatMatcher.group());
					setImpact(impacterName, impactedName, val);
				} else {
					throw new PatternSyntaxException("line [" + line
							+ "] : could not get a float for server name "
							+ impactedName, FLOAT_PATTERN, -1);
				}
			}
		} else {
			throw new PatternSyntaxException("line [" + line
					+ "] : could not match first server name", NAME_PATTERN, -1);
		}
	}

	protected static final String replaceForQuotes = java.util.regex.Matcher
	.quoteReplacement("\\\"");

	protected static final String replaceForEscapedQuotes = java.util.regex.Matcher
	.quoteReplacement("\"");

	public static String escapeDQuotes(String toParse) {
		return toParse.replaceAll("\"", replaceForQuotes);
	}

	public static String unEscapeDQuotes(String toParse) {
		return toParse.replaceAll("\\\\\"", replaceForEscapedQuotes);
	}

	/** @return a set containing all the servers referenced in this */
	public HashSet<String> getServerNames() {
		HashSet<String> ret = new HashSet<String>();
		for (Entry<String, HashMap<String, Double>> e : entrySet()) {
			ret.add(e.getKey());
			ret.addAll(e.getValue().keySet());
		}
		return ret;
	}

}
